package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.xgimi.commons.util.StringUtil;
import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.CommonMagicValue;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.SkillVersionBizService;
import com.xgimi.open.skill.datamanager.biz.service.WordStockBizService;
import com.xgimi.open.skill.datamanager.biz.utils.BusinessIdUtil;
import com.xgimi.open.skill.datamanager.biz.utils.DateUtil;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.domain.entity.EntryDO;
import com.xgimi.open.skill.datamanager.domain.entity.SkillVersionDO;
import com.xgimi.open.skill.datamanager.domain.entity.SlotDO;
import com.xgimi.open.skill.datamanager.domain.entity.WordStockDO;
import com.xgimi.open.skill.datamanager.domain.service.EntryService;
import com.xgimi.open.skill.datamanager.domain.service.SkillVersionService;
import com.xgimi.open.skill.datamanager.domain.service.SlotService;
import com.xgimi.open.skill.datamanager.domain.service.WordStockService;
import com.xgimi.open.skill.datamanager.service.request.BatchWordStockReqDTO;
import com.xgimi.open.skill.datamanager.service.request.EntryHandleReqDTO;
import com.xgimi.open.skill.datamanager.service.request.WordStockReqDTO;
import com.xgimi.open.skill.datamanager.service.request.WordValueReqDTO;
import com.xgimi.open.skill.datamanager.service.response.WordEntryDTO;
import com.xgimi.open.skill.datamanager.service.response.WordStockRespDTO;
import com.xgimi.open.skill.datamanager.service.response.WordStockRespDTO2;
import com.xgimi.open.skill.datamanager.service.response.page.IPageUtils;
import com.xgimi.open.skill.datamanager.service.response.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/8 14:32
 */
@Slf4j
@Service
public class WordStockBizServiceImpl implements WordStockBizService {

    // type 1：新增  2、修改  3、删除
    public static final Integer ADD_TYPE = 1;
    public static final Integer UPDATE_TYPE = 2;

    private final SkillVersionService skillVersionService;
    private final WordStockService wordStockService;
    private final RedisLockService redisLockService;
    private final SkillVersionBizService skillVersionBizService;
    private final SlotService slotService;
    private final EntryService entryService;
    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public WordStockBizServiceImpl(SkillVersionService skillVersionService,
                                   WordStockService wordStockService,
                                   RedisLockService redisLockService,
                                   SkillVersionBizService skillVersionBizService,
                                   SlotService slotService,
                                   EntryService entryService,
                                   GlogRecorder glogRecorder) {
        this.skillVersionService = skillVersionService;
        this.wordStockService = wordStockService;
        this.redisLockService = redisLockService;
        this.skillVersionBizService = skillVersionBizService;
        this.slotService = slotService;
        this.entryService = entryService;
        this.glogRecorder = glogRecorder;
    }

    @Override
    public Boolean addWordStock(WordStockReqDTO wordStockReqDTO) {
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(wordStockReqDTO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            WordStockDO wordStockDO = new WordStockDO();
            BeanUtils.copyProperties(wordStockReqDTO, wordStockDO);
            wordStockDO.setCreatedBy(wordStockReqDTO.getCreatedBy());
            // 1、查询技能版本表中 某个技能版本 是否已有重复的wordStock，根据wordStockName判断
            List<WordStockDO> list = wordStockService.queryWordStockByNameAndVid(wordStockDO);
            if (!CollectionUtils.isEmpty(list)) {
                throw new BizException(ExceptionErrorCode.WORDSTOCK_NAME_ALREADY_EXISTS);
            }
            // 2、新增wordStock
            wordStockDO.setBusinessId(BusinessIdUtil.getWordStockUUID());
            ret = wordStockService.saveWordStock(wordStockDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordAdd(wordStockReqDTO.getCreatedBy(),
                "wordStock", "addWordStock",
                String.valueOf(ret), wordStockReqDTO);
        return ret;
    }

    @Override
    public List<WordStockRespDTO2> list(WordStockReqDTO wordStockReqDTO) {
        Long skillVid = wordStockReqDTO.getSkillVid();
        skillVersionBizService.checkSkillVersion(skillVid);
        List<WordStockDO> wordStockDOs = wordStockService.listBySkillVid(skillVid);
        List<WordStockRespDTO2> res = getWordStockRespDTO2(wordStockDOs);
        return res;
    }

    private List<WordStockRespDTO2> getWordStockRespDTO2(List<WordStockDO> wordStockDOs) {
        List<WordStockRespDTO2> res = new ArrayList<>();
        List<Long> wordStockIds = wordStockDOs.stream().map(WordStockDO::getId).collect(Collectors.toList());
        List<EntryDO> entryDOList = entryService.listByWordStockIds(wordStockIds);
        Map<Long, List<EntryDO>> wordIdEntriesMap = entryDOList.stream().collect(Collectors.groupingBy(EntryDO::getWordStockId));
        for (WordStockDO wordStock : wordStockDOs) {
            WordStockRespDTO2 dto2 = new WordStockRespDTO2();
            BeanUtils.copyProperties(wordStock, dto2);
            List<EntryDO> entryDOS = wordIdEntriesMap.get(wordStock.getId());
            if (!CollectionUtils.isEmpty(entryDOS)) {
                List<WordEntryDTO> entries = entryDOS.stream().map(x -> {
                    WordEntryDTO wordEntryDTO = new WordEntryDTO();
                    BeanUtils.copyProperties(x, wordEntryDTO);
                    return wordEntryDTO;
                }).collect(Collectors.toList());
                dto2.setEntrys(entries);
            }
            res.add(dto2);
        }
        return res;
    }

    @Override
    public List<WordStockRespDTO> listBySkillVid(Long skillVid) {
        List<WordStockDO> wordStockDOs = wordStockService.listBySkillVid(skillVid);
        // 若有结果，则将 DO 转成 DTO
        List<WordStockRespDTO> list = wordStockDOs.stream().map(d -> {
            WordStockRespDTO dto = new WordStockRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return list;
    }

    @Override
    public Boolean deleteWordStock(WordStockReqDTO wordStockReqDTO) {
        BizAssert.notNull(wordStockReqDTO.getId(), ExceptionErrorCode.WORDSTOCK_ID_IS_NULL);
        // 先根据技能id查询 是否存在该词库
        WordStockDO res = wordStockService.queryById(wordStockReqDTO.getId());
        BizAssert.notNull(wordStockReqDTO.getId(), ExceptionErrorCode.WORDSTOCK_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(res.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            WordStockDO wordStockDO = new WordStockDO();
            BeanUtils.copyProperties(wordStockReqDTO, wordStockDO);
            // 若该词库已被槽位所关联 则删除失败
            List<SlotDO> slotDOS = slotService.queryByRelyonWordStock(wordStockDO.getId());
            if (!CollectionUtils.isEmpty(slotDOS)) {
                // 若不为空 则说明有槽位管理这个词库，则不允许删除
                throw new BizException(ExceptionErrorCode.BINDING_WORDSTOCK_CANNOT_DELETE);
            }
            // 删除技能
            ret = wordStockService.removeById(wordStockDO.getId());
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordDel(wordStockReqDTO.getUpdatedBy(),
                "wordStock", "deleteWordStock",
                wordStockReqDTO.toString());
        return ret;
    }

    @Override
    public Boolean updateWordStock(WordStockReqDTO wordStockReqDTO) {
        BizAssert.notNull(wordStockReqDTO.getId(), ExceptionErrorCode.WORDSTOCK_ID_IS_NULL);
        BizAssert.notNull(wordStockReqDTO.getSkillVid(), ExceptionErrorCode.SKILL_VERSION_ID_IS_NULL);
        // 先根据技能id查询 是否存在该词库
        WordStockDO from = wordStockService.queryById(wordStockReqDTO.getId());
        BizAssert.notNull(from, ExceptionErrorCode.WORDSTOCK_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(from.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            WordStockDO wordStockDO = new WordStockDO();
            BeanUtils.copyProperties(wordStockReqDTO, wordStockDO);
            wordStockDO.setUpdatedBy(wordStockReqDTO.getUpdatedBy());
            if (!StringUtils.isEmpty(wordStockDO.getWordName())) {
                // 判断当前 技能版本下 是否存在 相同名字的其他词库，则抛出异常
                List<WordStockDO> list = wordStockService.queryByNameByVidAndNoId(wordStockDO);
                if (!CollectionUtils.isEmpty(list)) {
                    throw new BizException(ExceptionErrorCode.WORDSTOCK_NAME_ALREADY_EXISTS);
                }
            }
            // 修改词库
            ret = wordStockService.updateById(wordStockDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        WordStockDO to = wordStockService.queryById(wordStockReqDTO.getId());
        glogRecorder.recordUpdate(wordStockReqDTO.getUpdatedBy(),
                "wordStock", "updateWordStock",
                String.valueOf(ret), from, to);
        return ret;
    }


    @Transactional
    @Override
    public Boolean batchCreate(BatchWordStockReqDTO batchWordStockReqDTO) {
        Long skillVid = batchWordStockReqDTO.getSkillVid();
        BizAssert.notNull(skillVid, ExceptionErrorCode.SKILL_VERSION_ID_IS_NULL);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(skillVid);
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            /* 前端传来的词库*/
            List<WordStockReqDTO> vos = batchWordStockReqDTO.getWordStockVOList();
            Set<String> vosNameSet = vos.stream().map(x -> x.getWordName()).collect(Collectors.toSet());
            /* 已存在的词库*/
            List<WordStockDO> dos = wordStockService.listBySkillVid(skillVid);
            /* vos中不存在，但dos存在的词库要删掉*/
            List<Long> batchDeleteList = new ArrayList<>();
            /* vos中存在，但dos不存在的词库要新增*/
            List<WordStockDO> batchSaveList = new ArrayList<>();
            /* vos中存在，且dos也存在的词库要更新*/
            List<WordStockDO> batchUpdateList = new ArrayList<>();

            HashMap<String, Long> nameIdMap = new HashMap<>();
            for (WordStockDO item : dos) {
                /* vos不存在，dos存在*/
                if (!vosNameSet.contains(item.getWordName())) {
                    batchDeleteList.add(item.getId());
                }
                nameIdMap.put(item.getWordName(), item.getId());
            }
            for (WordStockReqDTO vo : vos) {
                WordStockDO wsdo = new WordStockDO();
                wsdo.setSkillVid(skillVid);
                wsdo.setWordName(vo.getWordName());
                List<WordValueReqDTO> entry = vo.getEntry();
                for (WordValueReqDTO wordValueReqDTO : entry) {
                    wordValueReqDTO.setId(UUID.randomUUID().toString());
                    wordValueReqDTO.setCreatedAt(DateUtil.format(new Date()));
                }
                wsdo.setEntry(JSON.toJSONString(entry));
                if (nameIdMap.containsKey(vo.getWordName())) {
                    /* vos存在，dos存在*/
                    wsdo.setId(nameIdMap.get(vo.getWordName()));
                    wsdo.setUpdatedBy(batchWordStockReqDTO.getCreatedBy());
                    batchUpdateList.add(wsdo);
                } else {
                    /* vos存在，dos不存在*/
                    wsdo.setCreatedBy(batchWordStockReqDTO.getCreatedBy());
                    wsdo.setBusinessId(BusinessIdUtil.getWordStockUUID());
                    batchSaveList.add(wsdo);
                }
            }
            /* 批量删除vos中不存在的词库*/
            wordStockService.removeByIds(batchDeleteList);
            /* 批量更新已存在的词库*/
            wordStockService.updateBatchById(batchUpdateList);
            /* 批量新增不存在的词库*/
            ret = wordStockService.saveBatch(batchSaveList);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordAdd(batchWordStockReqDTO.getCreatedBy(),
                "wordStock", "batchCreate",
                String.valueOf(ret), batchWordStockReqDTO);
        return ret;
    }

    @Override
    public PageResult<WordStockRespDTO2> pageQuery(WordStockReqDTO wordStockReqDTO) {
        skillVersionBizService.checkSkillVersion(wordStockReqDTO.getSkillVid());
        WordStockDO wordStockDO = new WordStockDO();
        BeanUtils.copyProperties(wordStockReqDTO, wordStockDO);
        // 构造分页查询的入参
        IPage<WordStockDO> page = new Page<>(wordStockReqDTO.getCurrentPage(), wordStockReqDTO.getPageSize());
        // 调用domain层分页查询的方法
        IPage<WordStockDO> iPage = wordStockService.pageQuery(page, wordStockDO);
        // 若查询没结果，则返回空pageResult
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            log.info("no such wordStock!");
            return new PageResult<>(Lists.newArrayList(), IPageUtils.noDataPaginator(wordStockReqDTO.getPageSize()));
        }
        List<WordStockRespDTO2> list = getWordStockRespDTO2(iPage.getRecords());
        return new PageResult<>(list, IPageUtils.transformToPaginator(iPage));
    }

    @Override
    public WordStockRespDTO queryById(Long id) {
        WordStockRespDTO ret = new WordStockRespDTO();
        WordStockDO wordStockDO = wordStockService.queryById(id);
        BeanUtils.copyProperties(wordStockDO, ret);
        return ret;
    }

    @Override
    public List<WordEntryDTO> queryById2(Long id, String value) {
        List<EntryDO> entryDOList = entryService.listByWordStockIdAndValue(id, value);
        List<WordEntryDTO> entries = entryDOList.stream().map(x -> {
            WordEntryDTO wordEntryDTO = new WordEntryDTO();
            BeanUtils.copyProperties(x, wordEntryDTO);
            return wordEntryDTO;
        }).collect(Collectors.toList());
        return entries;
    }


    @Transactional
    @Override
    public Boolean handleEntry(EntryHandleReqDTO entryHandleReqDTO) {
        // TODO：glog
        Long wordStockId = entryHandleReqDTO.getWordStockId();
        Integer type = entryHandleReqDTO.getType();
        String value = entryHandleReqDTO.getValue();
        WordStockDO res = wordStockService.queryById(wordStockId);
        skillVersionBizService.checkSkillVersion(res.getSkillVid());
        BizAssert.notNull(wordStockId, ExceptionErrorCode.WORDSTOCK_ID_IS_NULL);
        BizAssert.notNull(wordStockId, ExceptionErrorCode.ENTRY_HANDLE_TYPE_ISNULL);
        BizAssert.notEmpty(value, ExceptionErrorCode.ENTRY_HANDLE_VALUE_ISNULL);
        if (StringUtils.isEmpty(entryHandleReqDTO.getSynonyms())) {
            entryHandleReqDTO.setSynonyms(value);
        } else {
            entryHandleReqDTO.setSynonyms(unRepeat(entryHandleReqDTO.getSynonyms()));
        }
        if (ADD_TYPE.equals(type)) {
            return addEntry(entryHandleReqDTO);
        } else if (UPDATE_TYPE.equals(type)) {
            return updateEntry(entryHandleReqDTO);
        } else {
            return deleteEntry(entryHandleReqDTO);
        }
    }

    private String unRepeat(String synonyms) {
        String[] split = synonyms.split(CommonMagicValue.CHINESE_DOUHAO);
        Set<String> words = Stream.of(split).collect(Collectors.toSet());
        return setToStr(words);
    }

    private Boolean addEntry(EntryHandleReqDTO entryHandleReqDTO) {
        Long wordStockId = entryHandleReqDTO.getWordStockId();
        WordStockDO wordStockDO = wordStockService.getById(wordStockId);
        SkillVersionDO skillVersionDO = skillVersionService.querySkillVersionById(wordStockDO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            String value = entryHandleReqDTO.getValue();
            List<EntryDO> entryDOS = entryService.listByWordStockId(wordStockId);
            // 遍历该词库的所有词条列表，若已存在相同名称的词条，则直接将新的同义词加在原有的词条上
            for (EntryDO entry : entryDOS) {
                if (value.equals(entry.getValue())) {
                    // 之前的同义词
                    String[] split1 = entry.getSynonyms().split(CommonMagicValue.CHINESE_DOUHAO);
                    Set<String> oldSet = Stream.of(split1).collect(Collectors.toSet());
                    // 新增的同义词
                    String[] split2 = entryHandleReqDTO.getSynonyms().split(CommonMagicValue.CHINESE_DOUHAO);
                    Set<String> newAddSet = Stream.of(split2).collect(Collectors.toSet());
                    // 合并同义词
                    oldSet.addAll(newAddSet);
                    entry.setSynonyms(setToStr(oldSet));
                    entry.setUpdatedBy(entryHandleReqDTO.getHandleBy());
                    return entryService.updateById(entry);
                }
            }
            // 若不存在相同名称的词条，则直接新增即可
            EntryDO entryDO = new EntryDO();
            BeanUtils.copyProperties(entryHandleReqDTO, entryDO);
            entryDO.setCreatedBy(entryHandleReqDTO.getHandleBy());
            ret = entryService.save(entryDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordAdd(entryHandleReqDTO.getHandleBy(),
                "entry", "addEntry",
                String.valueOf(ret), entryHandleReqDTO);
        return ret;
    }

    private Boolean updateEntry(EntryHandleReqDTO entryHandleReqDTO) {
        Long entryId = entryHandleReqDTO.getId();
        BizAssert.notNull(entryId, ExceptionErrorCode.ENTRY_ID_IS_NULL);
        String newValue = entryHandleReqDTO.getValue();
        String newSynonyms = entryHandleReqDTO.getSynonyms();
        EntryDO targetEntry = entryService.getById(entryId);
        String oldValue = targetEntry.getValue();
        if (oldValue.equals(newValue)) {
            // 若value值没有修改  则直接覆盖synonyms即可
            targetEntry.setSynonyms(newSynonyms);
            boolean ret = entryService.updateById(targetEntry);
            // 添加glog
            EntryDO to = entryService.getById(entryId);
            glogRecorder.recordUpdate(entryHandleReqDTO.getHandleBy(),
                    "entry", "updateEntry",
                    String.valueOf(ret), targetEntry, to);
            return ret;
        } else {
            // 若value值也被修改 则要考虑两种情况：1、新修改value不在之前的词条列表中  2、 之前的词条列表中已存在这个value，此时需要将两条数据整合成一条
            EntryDO oldEntry = entryService.getByValue(newValue);
            targetEntry.setValue(newValue);
            if (Objects.isNull(oldEntry)) {
                // 1、若不存在 则直接设置synonyms即可
                targetEntry.setSynonyms(newSynonyms);
                return entryService.updateById(targetEntry);
            } else {
                // 2、若存在 则将老的词条内容 整合到新的词条上
                // 之前的同义词
                String[] split1 = oldEntry.getSynonyms().split(CommonMagicValue.CHINESE_DOUHAO);
                Set<String> oldSet = Stream.of(split1).collect(Collectors.toSet());
                // 新增的同义词
                String[] split2 = newSynonyms.split(CommonMagicValue.CHINESE_DOUHAO);
                Set<String> newSet = Stream.of(split2).collect(Collectors.toSet());
                newSet.addAll(oldSet);
                targetEntry.setSynonyms(setToStr(newSet));

                // 最后删掉之前的词条
                entryService.removeById(oldEntry);
                boolean ret = entryService.updateById(targetEntry);
                // 添加glog
                EntryDO to = entryService.getById(entryId);
                glogRecorder.recordUpdate(entryHandleReqDTO.getHandleBy(),
                        "entry", "updateEntry",
                        String.valueOf(ret), targetEntry, to);
                // 更新新词条
                return ret;
            }
        }
    }

    private Boolean deleteEntry(EntryHandleReqDTO entryHandleReqDTO) {
        Long entryId = entryHandleReqDTO.getId();
        BizAssert.notNull(entryId, ExceptionErrorCode.ENTRY_ID_IS_NULL);
        boolean ret = entryService.removeById(entryId);
        glogRecorder.recordDel(entryHandleReqDTO.getHandleBy(),
                "entry", "deleteEntry",
                entryHandleReqDTO.toString());
        return ret;
    }

    private String setToStr(Set<String> set) {
        StringBuilder str = new StringBuilder();
        for (String s : set) {
            if (StringUtil.isBlank(s))
                continue;
            str.append(s).append(CommonMagicValue.CHINESE_DOUHAO);
        }
        String setStr = str.toString();
        return setStr.substring(0, setStr.length() - 1);
    }

    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }
}
